#!/usr/bin/env python
# -*- coding: UTF8 -*-

# portemu.py
#
#  Copyright (c) 2006 Marcelo Lira dos Santos
#
#  Authors: Marcelo Lira dos Santos <setanta@gmail.com>
#           Lauro Moura <lauromoura@gmail.com>
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License as
#  published by the Free Software Foundation; either version 2 of the
#  License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
#  USA

import socket
import binascii

import pygtk
pygtk.require("2.0")
import gtk
import gobject
import gtk.glade

from preferences import Preferences
from qemu_manager import QemuManager

PARALLEL = QemuManager.PARALLEL
SERIAL = QemuManager.SERIAL
COL_DECIMAL = 0

class AppWindow:
    def __init__(self):
        self.qemu = QemuManager()
        self.qemu.connect('data-received', self.on_data_received)
        self.qemu.connect('connection-closed', self.on_connection_closed)

        self.prefs = Preferences('portemu.cfg', self.qemu)

        self.wtree = gtk.glade.XML('portemu.glade')
        self.window = self.wtree.get_widget('portemu')
        self.qemu_button = self.wtree.get_widget('qemu_button')

        # Preference Dialog ---------------------------------------------------
        self.prefs_dialog = self.wtree.get_widget('preferences_dialog')
        self.prefs_dialog.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                      gtk.STOCK_OK, gtk.RESPONSE_OK)

        self.qemu_fc = self.wtree.get_widget('qemu_fc')
        self.bios_fc = self.wtree.get_widget('bios_fc')
        self.serial_spin = self.wtree.get_widget('serial_spin')
        self.parallel_spin = self.wtree.get_widget('parallel_spin')

        self.image_fc = self.wtree.get_widget('image_fc')
        file_filter = gtk.FileFilter()
        file_filter.add_pattern("*.img")
        file_filter.add_pattern("*.qcow")
        self.image_fc.set_filter(file_filter)

        self.from_view = [None, None]
        self.from_model = [None, None]
        self.to_view = [None, None]
        self.to_model = [None, None]

        # Serial Port ---------------------------------------------------------
        self.serialport_entry = self.wtree.get_widget('serialport_entry')
        self.sp_bit = []

        for i in range(0, 8):
            self.sp_bit.append(self.wtree.get_widget('sp_bit_' + str(i)))

        self.from_view[SERIAL], self.from_model[SERIAL] = \
                self.init_list('from_serial_view')
        self.to_view[SERIAL], self.to_model[SERIAL] = \
                self.init_list('to_serial_view')

        # Parallel Port -------------------------------------------------------
        self.parport_entry = self.wtree.get_widget('parport_entry')
        self.pp_bit = []

        for i in range(0, 8):
            self.pp_bit.append(self.wtree.get_widget('pp_bit_' + str(i)))

        self.from_view[PARALLEL], self.from_model[PARALLEL] = \
                self.init_list('from_parallel_view')
        self.to_view[PARALLEL], self.to_model[PARALLEL] = \
                self.init_list('to_parallel_view')

        self.controls_table = self.wtree.get_widget('controls_table')
        self.controls_table.set_sensitive(False)

        self.wtree.signal_autoconnect(self)
        self.window.show_all()

    # Callbacks ---------------------------------------------------------------
    def on_exit_button_clicked(self, widget):
        self.exit()

    def on_portemu_delete_event(self, window, event=None):
        self.exit()

    def exit(self):
        self.qemu.stop()
        gtk.main_quit()

    def on_qemu_button_toggled(self, qemu_button):
        if qemu_button.get_active():
            def do_connect():
                for port in (SERIAL, PARALLEL):
                    try:
                        self.qemu.init_connection(port)
                        self.controls_table.set_sensitive(True)

                    except socket.error, (value, msg):
                        self.show_error_message(msg)
                        self.controls_table.set_sensitive(False)

            if self.qemu.start():
                self.set_running_view()
                gobject.timeout_add(1500, do_connect)

        else:
            self.set_stopped_view()
            self.qemu.stop()

    def on_prefs_button_clicked(self, widget):
        self.qemu_fc.set_filename(self.qemu.get_property('qemu-binary'))
        self.bios_fc.set_filename(self.qemu.get_property('bios-dir'))
        self.image_fc.set_filename(self.qemu.get_property('image-file'))
        self.serial_spin.set_value(self.qemu.get_property('serial-port'))
        self.parallel_spin.set_value(self.qemu.get_property('parallel-port'))
        self.prefs_dialog.show()

    def on_preferences_dialog_delete_event(self, dialog, event):
        dialog.hide()

    def on_preferences_dialog_response(self, dialog, response):
        if response == gtk.RESPONSE_OK:
            self.prefs.save(self.qemu_fc.get_filename(),
                            self.image_fc.get_filename(),
                            self.bios_fc.get_filename(),
                            self.serial_spin.get_value_as_int(),
                            self.parallel_spin.get_value_as_int())

        dialog.hide()

    def on_bits_changed(self, button):
        if button in self.sp_bit:
            hexentry = self.serialport_entry
            bits = self.sp_bit
        else:
            hexentry = self.parport_entry
            bits = self.pp_bit

        value = self.get_decimal(bits)
        hexentry.set_text('%s (%d)' % (hex(value), value))

    def on_send_button_clicked(self, button):
        if button.name == 'send_serial_button':
            bits = self.sp_bit
            port = SERIAL
        else:
            bits = self.pp_bit
            port = PARALLEL

        if self.qemu.is_up(port):
            value = self.get_decimal(bits)

            try:
                self.qemu.send(port, chr(value))
                self.to_model[port].insert(0, [value])

            except socket.error, (value, msg):
                self.show_error_message(msg)

            except:
                pass

    def on_data_received(self, qemu_manager, port, value):
        self.from_model[port].insert(0, [value])

    def on_connection_closed(self, qemu_manager, port):
        self.set_stopped_view()
        self.qemu.stop()

    # Metodos de apoio --------------------------------------------------------
    def init_list(self, listname):
        view = self.wtree.get_widget(listname)
        model = gtk.ListStore(int)
        view.set_model(model)

        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn('Decimal', renderer, text=COL_DECIMAL)
        view.append_column(column)

        def do_format_hex(column, cell, model, iter, colnum):
            cell.set_property('text', hex(model[iter][colnum]))

        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn('Hexa', renderer)
        column.set_cell_data_func(renderer, do_format_hex, COL_DECIMAL)
        view.append_column(column)

        def do_format_binary(column, cell, model, iter, colnum):
            bin = ''
            value = model[iter][colnum]
            for i in range(8):
                bin = str(value % 2) + bin
                value = value / 2
            cell.set_property('text', bin)

        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn('Binário', renderer)
        column.set_cell_data_func(renderer, do_format_binary, COL_DECIMAL)
        view.append_column(column)

        return (view, model)

    def show_error_message(self, msg):
        msg = gtk.MessageDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT,
                                gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
        msg.run()
        msg.destroy()

    def get_decimal(self, bits):
        bit_list = [str(int(bit.get_active())) for bit in bits]
        bit_list.reverse()
        return int(''.join(bit_list), 2)

    def set_running_view(self):
        self.qemu_button.set_stock_id('gtk-media-stop')
        self.controls_table.set_sensitive(False)

    def set_stopped_view(self):
        self.qemu_button.set_stock_id('gtk-media-play')
        self.qemu_button.set_active(False)
        self.controls_table.set_sensitive(False)

    def run(self):
        gtk.main()


def run_app():
    app = AppWindow()
    app.run()

if __name__ == "__main__":
    run_app()
